﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Globalization;
using System.Linq.Expressions;
using System.Linq;
using System.Reflection;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace CavinatorService.DAO.Extensao
{
    public static class Util
    {
        #region Atributos

        static CultureInfo culture;

        delegate T ObjectActivator<T>(params object[] args);

        #endregion

        /// <summary>
        /// Construtor padrão
        /// </summary>
        static Util()
        {
            culture = new CultureInfo("pt-BR");
        }

        #region Métodos

        /// <summary>
        /// Método que verifica se um DataRow contém uma determinada coluna
        /// </summary>
        /// <typeparam name="T">Tipo de retorno que a coluna deveria conter</typeparam>
        /// <param name="drLinha">DataRow a ser examinado</param>
        /// <param name="strNome">Noma da coluna que deveria estar contida no DataRow</param>
        /// <param name="valorPadrao">Valor padrão de retorno, caso drLinha não esteja contida no DataRow ou tenha um valor nulo</param>
        /// <returns>Retorna o valor da coluna do tipo informado se não for nulo, se não retorna o valor padrão</returns>
        public static T VerificarColuna<T>(DataRow drLinha, string strNome, T valorPadrao)
        {
            //drLinha.Table.DefaultView.RowFilter = string.Format("ColumnName= '{0}'", strNome);
            //if (drLinha.Table.DefaultView.Count > 0 && drLinha[strNome] != null && !(drLinha[strNome] is DBNull))
            //{
            try
            {
                if (!(drLinha[strNome] is DBNull))
                {
                    if (typeof(T).ToString().ToUpper().Contains("NULLABLE"))
                        return (T)drLinha[strNome];

                    return (T)Convert.ChangeType(drLinha[strNome], typeof(T));
                }
            }
            catch (Exception) { }


            return valorPadrao;
        }

        /// <summary>
        /// Método que verifica de uma linha apontada pelo SqlDataReader contém uma determinada coluna e se seu valor não é nulo
        /// </summary>
        /// <typeparam name="T">Tipo de retorno que a coluna deveria conter</typeparam>
        /// <param name="sdrLinha">SqlDataReader a ser examinado</param>
        /// <param name="strNome">Noma da coluna que deveria estar contida no SqlDataReader</param>
        /// <param name="valorPadrao">Valor padrão de retorno, caso sdrLinha não esteja contida no DataRow ou tenha um valor nulo</param>
        /// <returns>Retorna o valor da coluna do tipo informado se não for nulo, se não retorna o valor padrão</returns>
        public static T VerificarColuna<T>(IDataReader sdrLinha, string strNome, T valorPadrao)
        {
            try
            {
                if (!(sdrLinha[strNome] is DBNull))
                {
                    if (typeof(T).ToString().ToUpper().Contains("NULLABLE"))
                        return (T)sdrLinha[strNome];

                    return (T)Convert.ChangeType(sdrLinha[strNome], typeof(T));
                }
            }
            catch (Exception) { }

            return valorPadrao;
        }

        /// <summary>
        /// Método que verifica se existe uma coluna no DataRow
        /// </summary>
        /// <param name="drLinha">DataRow com as informações</param>
        /// <param name="strNome">Nome da coluna</param>
        /// <returns>Se existe ou não</returns>
        public static bool VerificaExistencia(DataRow drLinha, string strNome)
        {
            return (drLinha.Table.Columns.Contains(strNome) && drLinha[strNome] != null && !(drLinha[strNome] is DBNull));
        }

        /// <summary>
        /// Método que verifica se existe uma coluna no SqlDataReader
        /// </summary>
        /// <param name="drLinha">DataRow com as informações</param>
        /// <param name="strNome">Nome da coluna</param>
        /// <returns>Se existe ou não</returns>
        public static bool VerificaExistencia(IDataReader sdrLinha, string strNome)
        {
            sdrLinha.GetSchemaTable().DefaultView.RowFilter = string.Format("ColumnName= '{0}'", strNome);
            return (sdrLinha.FieldCount > 0 && sdrLinha.GetSchemaTable().DefaultView.Count > 0 && sdrLinha[strNome] != null && !(sdrLinha[strNome] is DBNull));
        }

        /// <summary>
        /// Lista de Objetos
        /// </summary>
        /// <typeparam name="T">Tipo da Classe</typeparam>
        /// <param name="reader">Retorno do Banco</param>
        /// <returns>Lista de Objetos populados</returns>
        public static List<T> CriarListaObjeto<T>(IDataReader reader) where T : class , new()
        {
            List<T> lst = new List<T>();

            ObjectActivator<T> createdActivator = GetActivator<T>(Constructor(typeof(T)));

            try
            {
                while (reader.Read())
                    lst.Add((T)createdActivator(reader));
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                reader.Close();
            }

            return lst;
        }

        public static List<T> CriarListaObjeto<T>(IDataReader reader, string campoId, string campoNome) where T : class , new()
        {
            List<T> lst = new List<T>();

            ObjectActivator<T> createdActivator = GetActivator<T>(Constructor(typeof(T)));

            while (reader.Read())
                lst.Add((T)createdActivator(reader, campoId, campoNome));

            reader.Close();

            return lst;
        }

        public static Dictionary<TKey, List<TValue>> CriarDicionario<TKey, TValue>(this IDataReader reader, Func<TValue, TKey> keySelector)
            where TKey : IEquatable<TKey>
            where TValue : class, new()
        {
            List<TValue> lista = CriarListaObjeto<TValue>(reader);

            return lista.GroupBy(keySelector).ToDictionary(ks => ks.Key, ks => ks.ToList());
        }

        public static Dictionary<TKey, List<TValue>> CriarDicionario<TKey, TValue>(this IDataReader reader, Func<TValue, TKey> keySelector, string campoId, string campoNome)
            where TKey : IEquatable<TKey>
            where TValue : class, new()
        {
            List<TValue> lista = CriarListaObjeto<TValue>(reader, campoId, campoNome);

            return lista.GroupBy(keySelector).ToDictionary(ks => ks.Key, ks => ks.ToList());
        }

        /// <summary>
        /// Cria um objeto
        /// </summary>
        /// <typeparam name="T">Tipo da Classe</typeparam>
        /// <param name="reader">Retorno do banco</param>
        /// <returns>Objeto populado</returns>
        public static T CriarObjeto<T>(IDataReader reader) where T : class , new()
        {
            if (reader.Read())
            {
                ObjectActivator<T> createdActivator = GetActivator<T>(Constructor(typeof(T)));

                T instancia = (T)createdActivator(reader);
                reader.Close();
                return instancia;
            }

            reader.Close();

            return new T();
        }

        public static T CriarObjeto<T>(IDataReader reader, string campoId, string campoNome) where T : class , new()
        {
            if (reader.Read())
            {
                ObjectActivator<T> createdActivator = GetActivator<T>(Constructor(typeof(T)));
                T instancia = (T)createdActivator(reader, campoId, campoNome);
                reader.Close();
                return instancia;
            }

            reader.Close();

            return new T();
        }

        public static DataTable Popular(IDataReader reader, string dataTableName = "")
        {
            DataTable dt = new DataTable(dataTableName);

            dt.Load(reader);

            return dt;
        }

        public static void WriteToCsvFile(this DataTable dataTable, string filePath)
        {
            StringBuilder fileContent = new StringBuilder();

            foreach (var col in dataTable.Columns)
            {
                fileContent.Append(col.ToString() + ";");
            }

            fileContent.AppendLine();

            foreach (DataRow dr in dataTable.Rows)
            {

                foreach (var column in dr.ItemArray)
                {
                    fileContent.Append("\"" + column.ToString() + "\";");
                }

                fileContent.AppendLine();
            }

            File.WriteAllText(filePath, fileContent.ToString());

        }

        /// <summary>
        /// Usado para debug
        /// </summary>
        /// <typeparam name="T">Tipo da lista</typeparam>
        /// <param name="data">Lista de dados</param>
        /// <returns>DataTable de retorno</returns>
        public static DataTable ConvertToDatatableSimple<T>(IList<T> data)
        {
            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));
            DataTable table = new DataTable();

            for (int i = 0; i < props.Count; i++)
            {
                PropertyDescriptor prop = props[i];
                table.Columns.Add(prop.Name, prop.PropertyType);
            }

            object[] values = new object[props.Count];

            foreach (T item in data)
            {
                for (int i = 0; i < values.Length; i++)
                {
                    values[i] = props[i].GetValue(item);
                }

                table.Rows.Add(values);
            }

            return table;
        }

        #region Reflections

        private static ObjectActivator<T> GetActivator<T>(ConstructorInfo ctor)
        {
            Type type = ctor.DeclaringType;
            ParameterInfo[] paramsInfo = ctor.GetParameters();

            //Criando um unico parametro object[]
            ParameterExpression param =
                Expression.Parameter(typeof(object[]), "args");

            Expression[] argsExp =
                new Expression[paramsInfo.Length];

            //pega cada argumento do array de parametros
            //and create a typed expression of them
            for (int i = 0; i < paramsInfo.Length; i++)
            {
                Expression index = Expression.Constant(i);
                Type paramType = paramsInfo[i].ParameterType;

                Expression paramAccessorExp =
                    Expression.ArrayIndex(param, index);

                Expression paramCastExp =
                    Expression.Convert(paramAccessorExp, paramType);

                argsExp[i] = paramCastExp;
            }

            //make a NewExpression that calls the
            //ctor with the args we just created
            NewExpression newExp = Expression.New(ctor, argsExp);

            //create a lambda with the New
            //Expression as body and our param object[] as arg
            LambdaExpression lambda =
                Expression.Lambda(typeof(ObjectActivator<T>), newExp, param);

            //compile it
            ObjectActivator<T> compiled = (ObjectActivator<T>)lambda.Compile();
            return compiled;
        }

        private static ConstructorInfo Constructor(Type T)
        {
            ConstructorInfo[] constructs = T.GetConstructors();

            foreach (var c in constructs)
                if (c.ToString().Contains("DataReader"))
                    return c;

            return null;
        }



        #endregion

        #endregion
    }
}
